package com.kaibes.core.base.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class ProxyHandler<E> implements InvocationHandler {

    private E target;
    private Object proxy;
    private ProxyInvokeListener invokelistener;
    private Class<?>[] proxys;
    private Map<Method, Method> methodMap = new HashMap<>();
    
    private ProxyHandler(E target, Class<?>... proxys) {
        this(target, target.getClass().getClassLoader(), proxys);
    }
    
    private ProxyHandler(E target, ClassLoader loader, Class<?>... proxys) {
        this.target = target;
        this.proxys = proxys;
        if (proxys.length == 0) {
            proxys = target.getClass().getInterfaces();
        }
        proxy = Proxy.newProxyInstance(loader, proxys, this);
    }
    
    public void setInvokelistener(ProxyInvokeListener invokelistener) {
        this.invokelistener = invokelistener;
    }
    public ProxyInvokeListener getInvokelistener() {
        return invokelistener;
    }
    
    public E getTarget() {
        return target;
    }
    
    public boolean hasProxy(Class<?> proxy) {
        for (Class<?> check : proxys) {
            if (proxy == check) {
                return true;
            }
        }
        return false;
    }
    
    @SuppressWarnings("unchecked")
    public <T> T getProxy() {
        return (T) proxy;
    }
    
    public Class<?>[] getProxys() {
        return proxys;
    }
    
    @Override
    public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
        if (target instanceof ProxyTarget) {
            ProxyTarget myTarget = (ProxyTarget) target;
            return myTarget.invoke(proxy, method, args);
        } else {
            Method targetMethod = methodMap.get(method);
            if (targetMethod == null) {
                targetMethod = target.getClass().getMethod(method.getName(), method.getParameterTypes());
                methodMap.put(method, targetMethod);
            }
            if (invokelistener == null) {
                return targetMethod.invoke(target, args);
            } else {
                boolean flag = invokelistener.beforeInvoke(proxy, method, args);
                Object result = null;
                if (flag) {
                    result = invokelistener.doInvoke(method, targetMethod, args);
                }
                result = invokelistener.afterInvoke(result);
                return result;
            }
        }
    }

    public final static <E> ProxyHandler<E> getNewInstance(E target) {
        if (target == null) {
            return null;
        }
        return new ProxyHandler<E>(target);
    }
    public final static <E> ProxyHandler<E> getNewInstance(E target, Class<?>... proxys) {
        if (target == null) {
            return null;
        }
        return new ProxyHandler<E>(target, proxys);
    }
    public final static <E> ProxyHandler<E> getNewInstance(E target, ClassLoader loader, Class<?>... proxys) {
        if (target == null) {
            return null;
        }
        return new ProxyHandler<E>(target, loader, proxys);
    }
}
